/*
 * Tranquil Java Integrated Development Environment
 *
 * The GNU General Public License Version 3
 *
 * Copyright (C) 2021 Autumn Lamonte
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author Autumn Lamonte [AutumnWalksTheLake@gmail.com] ⚧ Trans Liberation Now
 * @version 1
 */
package tjide.ui;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.ResourceBundle;

import gjexer.TAction;
import gjexer.TApplication;
import gjexer.TButton;
import gjexer.TCheckBox;
import gjexer.TComboBox;
import gjexer.TExceptionDialog;
import gjexer.TField;
import gjexer.TMessageBox;
import gjexer.TWindow;
import gjexer.bits.CellAttributes;

import tjide.project.AppType;
import tjide.project.FileTarget;
import tjide.project.JavaTarget;
import tjide.project.License;
import tjide.project.Project;

/**
 * This window is used to configure the per-project preferences.
 */
public class ProjectOptionsWindow extends TWindow {

    /**
     * Translated strings.
     */
    private static ResourceBundle i18n = ResourceBundle.getBundle(ProjectOptionsWindow.class.getName());

    // ------------------------------------------------------------------------
    // Constants --------------------------------------------------------------
    // ------------------------------------------------------------------------

    // ------------------------------------------------------------------------
    // Variables --------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * If true, this is a new project window rather than an options window.
     */
    private boolean newProject = false;

    /**
     * Project name.
     */
    private TField projectName;

    /**
     * Project author.
     */
    private TField projectAuthor;

    /**
     * Project date.
     */
    private TField projectDate;

    /**
     * Project license.
     */
    private TComboBox projectLicense;

    /**
     * Project type.
     */
    private TComboBox applicationType;

    /**
     * Project root directory.
     */
    private TField rootDir;

    /**
     * Source directory.
     */
    private TField sourceDir;

    /**
     * Build directory.
     */
    private TField buildDir;

    /**
     * Resources directory.
     */
    private TField resourcesDir;

    /**
     * API docs directory.
     */
    private TField apiDocsDir;

    /**
     * List of JARs on this project.
     */
    private TComboBox jarList;

    /**
     * Whether or not to build a fat JAR.
     */
    private TCheckBox fatJar;

    /**
     * Whether or not to create a LICENSE file for a new project.
     */
    private TCheckBox createLicenseFile;

    /**
     * Whether or not to create a "hello world" application for a new
     * project.
     */
    private TCheckBox createHelloWorld;

    /**
     * List of targets to open when this window closes.
     */
    private List<FileTarget> targetsToOpen = new ArrayList<FileTarget>();

    // ------------------------------------------------------------------------
    // Constructors -----------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Public constructor.
     *
     * @param parent the main application
     */
    public ProjectOptionsWindow(final TApplication parent) {
        this(parent, false);
    }

    /**
     * Public constructor.
     *
     * @param parent the main application
     * @param newProject if true, this is a new project
     */
    public ProjectOptionsWindow(final TApplication parent,
        final boolean newProject) {

        super(parent,
            (newProject == true ? i18n.getString("newProjectWindowTitle") :
                i18n.getString("windowTitle")),
            0, 0, 76, 23, MODAL | CENTERED);

        this.newProject = newProject;

        final TranquilApplication app = ((TranquilApplication) getApplication());

        final int buttonOffset = 13;

        // Create a status bar
        statusBar = newStatusBar(i18n.getString("statusBar"));

        // Project metadata
        addLabel(i18n.getString("projectName"), 3, 2, "ttext", false);
        projectName = addField(22, 2, 25, false, app.getProject().getName());
        addLabel(i18n.getString("projectAuthor"), 3, 3, "ttext", false);
        projectAuthor = addField(22, 3, 25, false,
            app.getProject().getAuthor());
        addLabel(i18n.getString("projectDate"), 3, 4, "ttext", false);
        projectDate = addField(22, 4, 25, false, app.getProject().getDate());
        addLabel(i18n.getString("projectLicense"), 3, 5, "ttext", false);
        projectLicense = addComboBox(22, 5, 13, License.getLicenseNames(),
            0, 6, null);
        projectLicense.setText(app.getProject().getLicense().getName(), false);
        if (newProject == true) {
            createLicenseFile = addCheckBox(36, 5,
                i18n.getString("createLicenseFile"), true);
        }

        addLabel(i18n.getString("projectType"), 3, 6, "ttext", false);

        ArrayList<String> appTypes = new ArrayList<String>();
        appTypes.add(i18n.getString("CONSOLE"));
        appTypes.add(i18n.getString("AWT"));
        appTypes.add(i18n.getString("SWING"));
        appTypes.add(i18n.getString("JEXER"));
        appTypes.add(i18n.getString("GJEXER"));
        applicationType = addComboBox(22, 6, 13, appTypes, 0, 6, null);
        applicationType.setText(app.getProject().getAppType().toString(),
            false);
        if (newProject == true) {
            createHelloWorld = addCheckBox(36, 6,
                i18n.getString("createHelloWorld"), true);
        }

        // Directories
        int row = 10;
        if (newProject == true) {
            addLabel(i18n.getString("rootDir"), 3, row, "ttext", false);
            rootDir = addField(22, row, 25, false,
                app.getProject().getRootDir());
            row++;
        }
        addLabel(i18n.getString("sourceDir"), 3, row, "ttext", false);
        sourceDir = addField(22, row, 25, false,
            app.getProject().getSourceDir());
        row++;
        addLabel(i18n.getString("resourcesDir"), 3, row, "ttext", false);
        resourcesDir = addField(22, row, 25, false,
            app.getProject().getResourcesDir());
        row++;
        addLabel(i18n.getString("buildDir"), 3, row, "ttext", false);
        buildDir = addField(22, row, 25, false,
            app.getProject().getBuildDir());
        row++;
        addLabel(i18n.getString("apiDocsDir"), 3, row, "ttext", false);
        apiDocsDir = addField(22, row, 25, false,
            app.getProject().getApiDocsDir());
        row++;

        // Additional jars
        addLabel(i18n.getString("jarList"), 3, row + 3, "ttext", false);
        jarList = addComboBox(22, row + 3, 25, app.getProject().getJars(),
            -1, 4, null);
        addButton(i18n.getString("addJarButton"), 50, row + 2,
            new TAction() {
                public void DO() {
                    try {
                        String newJarName = fileOpenBox(".",
                            gjexer.TFileOpenBox.Type.SELECT, "^.*\\.jar$");
                        if (newJarName != null) {
                            File newJarFile = new File(newJarName);
                            File base = new File(".",
                                app.getProject().getRootDir()).getAbsoluteFile();
                            String newJarRelPath = getRelativePath(base,
                                newJarFile);
                            List<String> jars;
                            jars = ProjectOptionsWindow.this.jarList.getList();
                            if (newJarRelPath != null) {
                                jars.add(newJarRelPath);
                            } else {
                                jars.add(newJarName);
                            }
                            ProjectOptionsWindow.this.jarList.setList(jars);
                        }
                    } catch (IOException e) {
                        new TExceptionDialog(getApplication(), e);
                    }
                }
            });
        addButton(i18n.getString("removeJarButton"), 50, row + 4,
            new TAction() {
                public void DO() {
                    List<String> jars;
                    String jar = ProjectOptionsWindow.this.jarList.getText();
                    jars = ProjectOptionsWindow.this.jarList.getList();
                    jars.remove(jar);
                    ProjectOptionsWindow.this.jarList.setList(jars);
                }
            });

        // Build fat jar boolean
        fatJar = addCheckBox(3, row + 4, i18n.getString("buildFatJar"),
            app.getProject().getBuildFatJar());

        // Buttons
        addButton(i18n.getString("saveButton"), getWidth() - buttonOffset, 2,
            new TAction() {
                public void DO() {
                    // Copy values from window to properties, save and close
                    // window.
                    copyOptions();
                    if (newProject == true) {
                        if (createNewProject() == false) {
                            // We had some error creating the project, bail
                            // out.
                            return;
                        }
                    }
                    // Project exists and is OK, save options.
                    saveOptions();
                    ProjectOptionsWindow.this.close();
                    for (FileTarget target: targetsToOpen) {
                        try {
                            app.openEditor(app.getProject(), target);
                        } catch (IOException e) {
                            // Show this exception to the user.
                            new TExceptionDialog(app, e);
                        }
                    }
                }
            });

        if (newProject == false) {
            addButton(i18n.getString("okButton"), getWidth() - buttonOffset, 4,
                new TAction() {
                    public void DO() {
                        // Copy values from window to properties, close window.
                        copyOptions();
                        ProjectOptionsWindow.this.close();
                    }
                });
        }

        TButton cancelButton = addButton(i18n.getString("cancelButton"),
            getWidth() - buttonOffset, (newProject == true ? 4 : 6),
            new TAction() {
                public void DO() {
                    // Don't copy anything, just close the window.
                    ProjectOptionsWindow.this.close();
                    if (newProject == true) {
                        app.closeProject(false);
                    }
                }
            });

        // Save this for last: make the cancel button default action.
        activate(cancelButton);
    }

    // ------------------------------------------------------------------------
    // Event handlers ---------------------------------------------------------
    // ------------------------------------------------------------------------

    // ------------------------------------------------------------------------
    // TWindow ----------------------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Draw the options panel.
     */
    @Override
    public void draw() {
        // Draw window and border.
        super.draw();

        CellAttributes boxColor = getTheme().getColor("ttext");

        // Project metadata
        drawBox(2, 2, 61, 9, boxColor, boxColor);
        putStringXY(4, 2, i18n.getString("projectTitle"), boxColor);

        // Directories
        drawBox(2, 10, 50, (newProject == true ? 17 : 16), boxColor, boxColor);
        putStringXY(4, 10, i18n.getString("directoriesTitle"), boxColor);

        // Java
        drawBox(2, (newProject == true ? 18 : 17), 50,
            (newProject == true ? 22 : 21), boxColor, boxColor);
        putStringXY(4, (newProject == true ? 18 : 17),
            i18n.getString("javaTitle"), boxColor);
    }

    // ------------------------------------------------------------------------
    // ProjectOptionsWindow ---------------------------------------------------
    // ------------------------------------------------------------------------

    /**
     * Copy options from window fields to the project properties.
     */
    private void copyOptions() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        app.getProject().setName(projectName.getText());
        // Also set the name in the project tree window.
        app.getProjectWindow().setProjectName(projectName.getText());
        app.getProject().setAuthor(projectAuthor.getText());
        app.getProject().setDate(projectDate.getText());
        app.getProject().setLicense(projectLicense.getText());
        app.getProject().setAppType(applicationType.getText());
        if (newProject == true) {
            String rootDirName = rootDir.getText();
            if (rootDirName.length() == 0) {
                rootDirName = ".";
            }
            app.getProject().setRootDir(rootDirName);
        }
        app.getProject().setSourceDir(sourceDir.getText());
        app.getProject().setResourcesDir(resourcesDir.getText());
        app.getProject().setBuildDir(buildDir.getText());
        app.getProject().setApiDocsDir(apiDocsDir.getText());
        app.getProject().setJars(jarList.getList());
        app.getProject().setBuildFatJar(fatJar.isChecked());
    }

    /**
     * Save project properties.
     */
    private void saveOptions() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        assert (app.getProject() != null);
        app.getProject().save();
    }

    /**
     * Computes the path for a file relative to a given base, or fails if the
     * only shared directory is the root and the absolute form is better.
     * Note package private access.
     *
     * Snippet pulled from https://stackoverflow.com/a/11215226 .
     *
     * @param base File that is the base for the result
     * @param name File to be "relativized"
     * @return the relative name, or null if these files have no common path
     * elements
     * @throws IOException of a java.io operation throws
     */
    static String getRelativePath(File base, File name) throws IOException {
        File parent = base.getParentFile();
        /*
        System.err.println("getRelativePath " + base + " " + name +
            " " + parent);
         */

        if (parent == null) {
            return null;
        }

        String bpath = base.getCanonicalPath();
        String fpath = name.getCanonicalPath();

        /*
        System.err.println("getRelativePath 1: bpath " + bpath +
            " fpath " + fpath);
         */

        if (bpath.equals(fpath)) {
            return "";
        }

        if (fpath.startsWith(bpath)) {
            /*
            System.err.println("getRelativePath 2: " +
                fpath.substring(bpath.length() + 1));
            */

            return fpath.substring(bpath.length() + 1);
        } else {
            return (".." + File.separator + getRelativePath(parent, name));
        }
    }

    /**
     * Create a new project based on the UI elements.
     *
     * @return true if the project was created successfully
     */
    private boolean createNewProject() {
        assert (newProject == true);
        assert (createLicenseFile != null);
        assert (createHelloWorld != null);
        TranquilApplication app = ((TranquilApplication) getApplication());

        // Ensure the root directory exists or can be created.
        String rootDirName = rootDir.getText();
        if (rootDirName.length() == 0) {
            rootDirName = ".";
        }
        File rootDirFile = new File(rootDirName);
        if (rootDirFile.exists() && !rootDirFile.isDirectory()) {
            // Error: root directory already exists and is not a file.
            messageBox(i18n.getString("rootDirExistsTitle"),
                MessageFormat.format(i18n.getString("rootDirExistsCaption"),
                    rootDirName),
                TMessageBox.Type.OK);
            return false;
        }

        // Ensure the source directory exists or can be created.
        String sourceDirName = sourceDir.getText();
        File sourceDirFile = new File(rootDirName, sourceDirName);
        if (sourceDirFile.exists() && !sourceDirFile.isDirectory()) {
            // Error: source directory already exists and is not a file.
            messageBox(i18n.getString("sourceDirExistsTitle"),
                MessageFormat.format(i18n.getString("sourceDirExistsCaption"),
                    sourceDirName),
                TMessageBox.Type.OK);
            return false;
        }

        // Ensure the resources directory exists or can be created..
        String resourcesDirName = resourcesDir.getText();
        File resourcesDirFile = new File(rootDirName, resourcesDirName);
        if (resourcesDirFile.exists() && !resourcesDirFile.isDirectory()) {
            // Error: resources directory already exists and is not a file.
            messageBox(i18n.getString("resourcesDirExistsTitle"),
                MessageFormat.format(i18n.
                    getString("resourcesDirExistsCaption"),
                    resourcesDirName),
                TMessageBox.Type.OK);
            return false;
        }

        // Ensure the build directory exists.
        String buildDirName = buildDir.getText();
        File buildDirFile = new File(rootDirName, buildDirName);
        if (buildDirFile.exists() && !buildDirFile.isDirectory()) {
            // Error: build directory already exists and is not a file.
            messageBox(i18n.getString("buildDirExistsTitle"),
                MessageFormat.format(i18n.getString("buildDirExistsCaption"),
                    buildDirName),
                TMessageBox.Type.OK);
            return false;
        }

        // Ensure the API docs dir exists.
        String apiDocsDirName = apiDocsDir.getText();
        File apiDocsDirFile = new File(rootDirName, apiDocsDirName);
        if (apiDocsDirFile.exists() && !apiDocsDirFile.isDirectory()) {
            // Error: apiDocs directory already exists and is not a file.
            messageBox(i18n.getString("apiDocsDirExistsTitle"),
                MessageFormat.format(i18n.getString("apiDocsDirExistsCaption"),
                    apiDocsDirName),
                TMessageBox.Type.OK);
            return false;
        }

        // Create all of the directories.
        if (!rootDirFile.exists()) {
            if (!rootDirFile.mkdirs()) {
                // Error: root directory cannot be created.
                messageBox(i18n.getString("rootDirCannotCreateTitle"),
                    MessageFormat.format(i18n.
                        getString("rootDirCannotCreateCaption"),
                        rootDirName),
                    TMessageBox.Type.OK);
                return false;
            }
        }
        if (!sourceDirFile.exists()) {
            if (!sourceDirFile.mkdirs()) {
                // Error: source directory cannot be created.
                messageBox(i18n.getString("sourceDirCannotCreateTitle"),
                    MessageFormat.format(i18n.
                        getString("sourceDirCannotCreateCaption"),
                        sourceDirName),
                    TMessageBox.Type.OK);
                return false;
            }
        }
        if (!resourcesDirFile.exists()) {
            if (!resourcesDirFile.mkdirs()) {
                // Error: resources directory cannot be created.
                messageBox(i18n.getString("resourcesDirCannotCreateTitle"),
                    MessageFormat.format(i18n.
                        getString("resourcesDirCannotCreateCaption"),
                        resourcesDirName),
                    TMessageBox.Type.OK);
                return false;
            }
        }
        if (!buildDirFile.exists()) {
            if (!buildDirFile.mkdirs()) {
                // Error: build directory cannot be created.
                messageBox(i18n.getString("buildDirCannotCreateTitle"),
                    MessageFormat.format(i18n.
                        getString("buildDirCannotCreateCaption"),
                        buildDirName),
                    TMessageBox.Type.OK);
                return false;
            }
        }
        if (!apiDocsDirFile.exists()) {
            if (!apiDocsDirFile.mkdirs()) {
                // Error: apiDocs directory cannot be created.
                messageBox(i18n.getString("apiDocsDirCannotCreateTitle"),
                    MessageFormat.format(i18n.
                        getString("apiDocsDirCannotCreateCaption"),
                        apiDocsDirName),
                    TMessageBox.Type.OK);
                return false;
            }
        }

        // If license file was specified, create it.
        if (createLicenseFile.isChecked()) {
            License license = License.getLicense(projectLicense.getText());
            if (license != null) {
                try {
                    File licenseFile = new File(rootDirFile, "LICENSE");
                    FileWriter output = new FileWriter(licenseFile);
                    String licenseText = license.getFullText();
                    String [] lines = licenseText.split("\n");
                    for (int i = 0; i < lines.length; i++) {
                        String line = lines[i];
                        line = substituteTokens(line);
                        output.write(line + "\n");
                    }
                    output.close();
                } catch (IOException e) {
                    // Show this exception to the user.
                    new TExceptionDialog(app, e);
                }
                app.getProjectWindow().addTarget("LICENSE",
                    NewTargetWindow.TargetType.TEXT);
            }
        }

        // If hello world was specified, create it.
        if (createHelloWorld.isChecked()) {
            AppType appType = Project.findAppType(applicationType.getText());
            switch (appType) {
            case CONSOLE:
                createConsoleHelloWorld();
                break;
            case AWT:
                createAwtHelloWorld();
                break;
            case SWING:
                createSwingHelloWorld();
                break;
            case JEXER:
                createJexerHelloWorld();
                break;
            case GJEXER:
                createGJexerHelloWorld();
                break;
            }
        }

        // Everything checks out OK.
        return true;
    }

    /**
     * Substitute year, author, and program name tags in license text.
     *
     * @param line a line of license text
     * @return the line with substitution
     */
    private String substituteTokens(String line) {
        line = line.replace("<year>", Integer.toString(
            Calendar.getInstance().get(Calendar.YEAR)));
        if (projectAuthor.getText().trim().length() > 0) {
            line = line.replace("<name of author>",
                projectAuthor.getText().trim());
        }
        if (projectName.getText().trim().length() > 0) {
            line = line.replace("<program>",
                projectName.getText().trim());
        }
        return line;
    }

    /**
     * Extract a resource in the jar file to a file.
     *
     * @param resource the name of the resource
     * @param file the file to save the resource data to
     * @throws IOException if a java.io operation throws
     */
    public void saveResourceToFile(String resource, File file)
        throws IOException {

        InputStream in = getClass().getClassLoader().
                                getResourceAsStream(resource);
        OutputStream out = new FileOutputStream(file);
        for (;;) {
            int ch = in.read();
            if (ch == -1) {
                break;
            }
            out.write(ch);
        }
        in.close();
        out.close();
    }

    /**
     * Create the Hello World for a console application.
     */
    private void createConsoleHelloWorld() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        String mainFilename = "Main.java";
        String rootDirName = rootDir.getText();
        if (rootDirName.trim().length() == 0) {
            rootDirName = ".";
        }
        String sourceDirName = sourceDir.getText();
        if (sourceDirName.trim().length() == 0) {
            sourceDirName = ".";
        }
        File sourceDirFile = new File(rootDirName, sourceDirName);
        File mainFile = new File(sourceDirFile, mainFilename);

        try {
            FileWriter output = new FileWriter(mainFile);

            // License text, substitute <year> and <author>.
            License license = License.getLicense(projectLicense.getText());
            if (license != null) {
                output.write("/**\n");
                String licenseHeader = license.getSourceHeader();
                String [] lines = licenseHeader.split("\n");
                for (int i = 0; i < lines.length; i++) {
                    String line = lines[i];
                    line = substituteTokens(line);
                    output.write(" * " + line + "\n");
                }
                output.write(" */\n");
                output.write("\n");
            }

            // Main class
            output.write("/**\n");
            output.write(" * Main class contains program entry point.\n");
            output.write(" */\n");
            output.write("public class Main {\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * HelloWorld program entry point.\n");
            output.write("     */\n");
            output.write("    public static void main(String [] args) {\n");
            output.write("        System.out.println(\"Hello World!\");\n");
            output.write("    }\n");
            output.write("}\n");
            output.close();

        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }

        // Add to targets to open.
        String targetName = sourceDirName + File.separator + mainFilename;
        JavaTarget target;
        target = (JavaTarget) app.getProjectWindow().addTarget(targetName,
            NewTargetWindow.TargetType.JAVA_SOURCE);
        target.setRunnable(true);
        targetsToOpen.add(target);
    }

    /**
     * Create the Hello World for a console application.
     */
    private void createAwtHelloWorld() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        String mainFilename = "HelloWorld.java";
        String rootDirName = rootDir.getText();
        if (rootDirName.trim().length() == 0) {
            rootDirName = ".";
        }
        String sourceDirName = sourceDir.getText();
        if (sourceDirName.trim().length() == 0) {
            sourceDirName = ".";
        }
        File sourceDirFile = new File(rootDirName, sourceDirName);
        File mainFile = new File(sourceDirFile, mainFilename);

        try {
            FileWriter output = new FileWriter(mainFile);

            // License text, substitute <year> and <author>.
            License license = License.getLicense(projectLicense.getText());
            if (license != null) {
                output.write("/**\n");
                String licenseHeader = license.getSourceHeader();
                String [] lines = licenseHeader.split("\n");
                for (int i = 0; i < lines.length; i++) {
                    String line = lines[i];
                    line = substituteTokens(line);
                    output.write(" * " + line + "\n");
                }
                output.write(" */\n");
                output.write("\n");
            }

            // Main class
            output.write("import java.awt.Frame;\n");
            output.write("import java.awt.event.WindowAdapter;\n");
            output.write("import java.awt.event.WindowEvent;\n");
            output.write("\n");
            output.write("\n");
            output.write("/**\n");
            output.write(" * HelloWorld is an empty frame with a label.\n");
            output.write(" */\n");
            output.write("public class HelloWorld extends Frame {\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * Public constructor.\n");
            output.write("     */\n");
            output.write("    public HelloWorld() {\n");
            output.write("        super(\"Hello World\");\n");
            output.write("\n");
            output.write("        // For AWT we do not have setDefaultCloseOperation,\n");
            output.write("        // add a listener so the normal close button works.\n");
            output.write("        addWindowListener(new WindowAdapter() {\n");
            output.write("\n");
            output.write("            public void windowClosing(WindowEvent we) {\n");
            output.write("                dispose();\n");
            output.write("            }\n");
            output.write("        });\n");
            output.write("\n");
            output.write("        setSize(300, 300);\n");
            output.write("        setVisible(true);\n");
            output.write("    }\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * HelloWorld program entry point.\n");
            output.write("     */\n");
            output.write("    public static void main(String [] args) {\n");
            output.write("        HelloWorld app = new HelloWorld();\n");
            output.write("    }\n");
            output.write("}\n");
            output.close();

        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }

        // Add to targets to open.
        String targetName = sourceDirName + File.separator + mainFilename;
        JavaTarget target;
        target = (JavaTarget) app.getProjectWindow().addTarget(targetName,
            NewTargetWindow.TargetType.JAVA_SOURCE);
        target.setRunnable(true);
        targetsToOpen.add(target);
    }

    /**
     * Create the Hello World for a console application.
     */
    private void createSwingHelloWorld() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        String mainFilename = "HelloWorld.java";
        String rootDirName = rootDir.getText();
        if (rootDirName.trim().length() == 0) {
            rootDirName = ".";
        }
        String sourceDirName = sourceDir.getText();
        if (sourceDirName.trim().length() == 0) {
            sourceDirName = ".";
        }
        File sourceDirFile = new File(rootDirName, sourceDirName);
        File mainFile = new File(sourceDirFile, mainFilename);

        try {
            FileWriter output = new FileWriter(mainFile);

            // License text, substitute <year> and <author>.
            License license = License.getLicense(projectLicense.getText());
            if (license != null) {
                output.write("/**\n");
                String licenseHeader = license.getSourceHeader();
                String [] lines = licenseHeader.split("\n");
                for (int i = 0; i < lines.length; i++) {
                    String line = lines[i];
                    line = substituteTokens(line);
                    output.write(" * " + line + "\n");
                }
                output.write(" */\n");
                output.write("\n");
            }

            // Main class
            output.write("import javax.swing.JFrame;\n");
            output.write("import javax.swing.WindowConstants;\n");
            output.write("\n");
            output.write("\n");
            output.write("/**\n");
            output.write(" * HelloWorld is an empty frame with a label.\n");
            output.write(" */\n");
            output.write("public class HelloWorld extends JFrame {\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * Public constructor.\n");
            output.write("     */\n");
            output.write("    public HelloWorld() {\n");
            output.write("        super(\"Hello World\");\n");
            output.write("\n");
            output.write("        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);\n");
            output.write("\n");
            output.write("        setSize(300, 300);\n");
            output.write("        setVisible(true);\n");
            output.write("    }\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * HelloWorld program entry point.\n");
            output.write("     */\n");
            output.write("    public static void main(String [] args) {\n");
            output.write("        HelloWorld app = new HelloWorld();\n");
            output.write("    }\n");
            output.write("}\n");
            output.close();

        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }

        // Add to targets to open.
        String targetName = sourceDirName + File.separator + mainFilename;
        JavaTarget target;
        target = (JavaTarget) app.getProjectWindow().addTarget(targetName,
            NewTargetWindow.TargetType.JAVA_SOURCE);
        target.setRunnable(true);
        targetsToOpen.add(target);
    }

    /**
     * Create the Hello World for a console application.
     */
    private void createJexerHelloWorld() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        String mainFilename = "HelloWorld.java";
        String rootDirName = rootDir.getText();
        if (rootDirName.trim().length() == 0) {
            rootDirName = ".";
        }
        String sourceDirName = sourceDir.getText();
        if (sourceDirName.trim().length() == 0) {
            sourceDirName = ".";
        }
        File sourceDirFile = new File(rootDirName, sourceDirName);
        File mainFile = new File(sourceDirFile, mainFilename);

        try {
            FileWriter output = new FileWriter(mainFile);

            // License text, substitute <year> and <author>.
            License license = License.getLicense(projectLicense.getText());
            if (license != null) {
                output.write("/**\n");
                String licenseHeader = license.getSourceHeader();
                String [] lines = licenseHeader.split("\n");
                for (int i = 0; i < lines.length; i++) {
                    String line = lines[i];
                    line = substituteTokens(line);
                    output.write(" * " + line + "\n");
                }
                output.write(" */\n");
                output.write("\n");
            }

            // Main class
            output.write("import jexer.TApplication;\n");
            output.write("\n");
            output.write("\n");
            output.write("/**\n");
            output.write(" * HelloWorld is an empty frame with some menus.\n");
            output.write(" */\n");
            output.write("public class HelloWorld extends TApplication {\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * Public constructor.\n");
            output.write("     */\n");
            output.write("    public HelloWorld() throws java.io.UnsupportedEncodingException {\n");
            output.write("        super(BackendType.XTERM);\n");
            output.write("    }\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * HelloWorld program entry point.\n");
            output.write("     */\n");
            output.write("    public static void main(String [] args) {\n");
            output.write("        try {\n");
            output.write("            HelloWorld app = new HelloWorld();\n");
            output.write("            app.addToolMenu();\n");
            output.write("            app.addFileMenu();\n");
            output.write("            app.addEditMenu();\n");
            output.write("            app.addWindowMenu();\n");
            output.write("            app.addHelpMenu();\n");
            output.write("            app.run();\n");
            output.write("        } catch (Throwable t) {\n");
            output.write("            t.printStackTrace();\n");
            output.write("            System.exit(-1);\n");
            output.write("        }\n");
            output.write("    }\n");
            output.write("}\n");
            output.close();

        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }

        // Add to targets to open.
        String targetName = sourceDirName + File.separator + mainFilename;
        JavaTarget target;
        target = (JavaTarget) app.getProjectWindow().addTarget(targetName,
            NewTargetWindow.TargetType.JAVA_SOURCE);
        target.setRunnable(true);
        targetsToOpen.add(target);

        // Add the Jexer library.
        app.getProject().addJar("jexer.jar");
        try {
            saveResourceToFile("jars" + File.separator + "jexer.jar",
                new File(app.getProject().getRootDir(),
                    app.getProject().getResourcesDir() + File.separator +
                    "jexer.jar"));
        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }
    }

    /**
     * Create the Hello World for a console application.
     */
    private void createGJexerHelloWorld() {
        TranquilApplication app = ((TranquilApplication) getApplication());
        String mainFilename = "HelloWorld.java";
        String rootDirName = rootDir.getText();
        if (rootDirName.trim().length() == 0) {
            rootDirName = ".";
        }
        String sourceDirName = sourceDir.getText();
        if (sourceDirName.trim().length() == 0) {
            sourceDirName = ".";
        }
        File sourceDirFile = new File(rootDirName, sourceDirName);
        File mainFile = new File(sourceDirFile, mainFilename);

        try {
            FileWriter output = new FileWriter(mainFile);

            // License text, substitute <year> and <author>.
            License license = License.getLicense(projectLicense.getText());
            if (license != null) {
                output.write("/**\n");
                String licenseHeader = license.getSourceHeader();
                String [] lines = licenseHeader.split("\n");
                for (int i = 0; i < lines.length; i++) {
                    String line = lines[i];
                    line = substituteTokens(line);
                    output.write(" * " + line + "\n");
                }
                output.write(" */\n");
                output.write("\n");
            }

            // Main class
            output.write("import gjexer.TApplication;\n");
            output.write("\n");
            output.write("\n");
            output.write("/**\n");
            output.write(" * HelloWorld is an empty frame with some menus.\n");
            output.write(" */\n");
            output.write("public class HelloWorld extends TApplication {\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * Public constructor.\n");
            output.write("     */\n");
            output.write("    public HelloWorld() throws java.io.UnsupportedEncodingException {\n");
            output.write("        super(BackendType.XTERM);\n");
            output.write("    }\n");
            output.write("\n");
            output.write("    /**\n");
            output.write("     * HelloWorld program entry point.\n");
            output.write("     */\n");
            output.write("    public static void main(String [] args) {\n");
            output.write("        try {\n");
            output.write("            HelloWorld app = new HelloWorld();\n");
            output.write("            app.addToolMenu();\n");
            output.write("            app.addFileMenu();\n");
            output.write("            app.addEditMenu();\n");
            output.write("            app.addWindowMenu();\n");
            output.write("            app.addHelpMenu();\n");
            output.write("            app.run();\n");
            output.write("        } catch (Throwable t) {\n");
            output.write("            t.printStackTrace();\n");
            output.write("            System.exit(-1);\n");
            output.write("        }\n");
            output.write("    }\n");
            output.write("}\n");
            output.close();

        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }

        // Add to targets to open.
        String targetName = sourceDirName + File.separator + mainFilename;
        JavaTarget target;
        target = (JavaTarget) app.getProjectWindow().addTarget(targetName,
            NewTargetWindow.TargetType.JAVA_SOURCE);
        target.setRunnable(true);
        targetsToOpen.add(target);

        // Add the GJexer library.
        app.getProject().addJar("gjexer.jar");
        try {
            saveResourceToFile("jars" + File.separator + "gjexer.jar",
                new File(app.getProject().getRootDir(),
                    app.getProject().getResourcesDir() + File.separator +
                    "gjexer.jar"));
        } catch (IOException e) {
            // Show this exception to the user.
            new TExceptionDialog(app, e);
        }
    }

}
